%%@ author Grupp 3
%%@ version 1.0
%%@ copyright Grupp 3
%%@ doc The module contains functions for operating a database of tasks.

-module(taskman).
-export([addTask/4, changeTaskStatus/4, deleteTask/2, listTasks/1, taskStatusList/2, sortTasks/4, detsAbs/3, sortTasksHelp/3]).

-include_lib("eunit/include/eunit.hrl").

%% @doc Creates a new task, with a generated unique ID, in the project. 
%%	Return ok if TaskName is not already taken, else error.
%% @spec addTask(ProjectID, TaskName, TaskDesc, Creator) -> atom() | error
%% where 
%% 	ProjectID = integer()
%% 	TaskName = string()
%%	TaskDesc = string()
%%	Creator = string()

addTask(ProjectID, TaskName, TaskDesc, Creator) -> 
	dets:open_file(integer_to_list(ProjectID),[]),	
	[X] = dets:lookup(integer_to_list(ProjectID), counter),

	case dets:insert_new(integer_to_list(ProjectID),{TaskName,TaskDesc,Creator,Creator,waiting,element(2,X)}) of
		true ->
			dets:insert(integer_to_list(ProjectID), {counter, (element(2,X)+1)}),
			dets:close(integer_to_list(ProjectID)),
			ok;
		_ -> 
			{error,"Task with that name already exists"}		
	end.


%% @doc Change the status of the task named TaskName to NewGroup. Return ok if the task with the ID TaskID exists, else error.
%% @spec changeTaskStatus(ProjectId, TaskID, NewGroup, User) -> atom() | error
%% where 
%% 	ProjectID = integer()
%%	TaskID = integer()
%%	Status = atom()
%%	User = string()

changeTaskStatus(ProjectID, TaskID, Status, User) ->
	dets:open_file(integer_to_list(ProjectID),[]),
	X = dets:match_object(integer_to_list(ProjectID), {'_','_','_','_','_',TaskID}),
		
	case X =/= [] of 

	true->
		dets:insert(integer_to_list(ProjectID),{element(1,hd(X)), element(2,hd(X)), User, element(4,hd(X)), Status, TaskID}),
		dets:close(integer_to_list(ProjectID));
	false ->
		{error, "Task with that ID doesn't exist"}
	end.
	

	
%% @doc Remove the task with ID TaskID from the project named ProjectName. Return ok if the task with ID TaskID exists, else error.
%% @spec deleteTask(ProjectID, TaskID) -> atom() | error
%% where 
%% 	ProjectID = integer()
%%	TaskID = integer()

deleteTask(ProjectID, TaskID) ->
    dets:open_file(integer_to_list(ProjectID),[]),
	
    X = dets:match_object(integer_to_list(ProjectID), {'_','_','_','_','_', TaskID}),
        
        case X =/= [] of 
			true ->
                dets:delete(integer_to_list(ProjectID), element(1,hd(X))),
                dets:close(integer_to_list(ProjectID));
            false -> 
                dets:close(integer_to_list(ProjectID)),
                {error, "Task with that name doesn't exist"}
    end.


%% @doc Return a tuple with ok and a list of all existing tasks with the status waiting, inprogress or done in the project named ProjectName. return error if the project named ProjectName doesn't exist in the database Projects.
%% @spec listTasks(ProjectID) -> {ok, list} | error
%% where 
%% 	ProjectID = integer()


listTasks(ProjectID) ->
	dets:open_file(integer_to_list(ProjectID), []),
	Waiting = dets:match_object(integer_to_list(ProjectID),{'$1','_','_','_',waiting,'_'}),
	Busy = dets:match_object(integer_to_list(ProjectID),{'$1','_','_','_',inprogress ,'_'}),
	Done = dets:match_object(integer_to_list(ProjectID),{'$1','_','_','_',done,'_'}),
	dets:close(integer_to_list(ProjectID)),
	{ok, {lists:keysort(6,Waiting), lists:keysort(6,Busy), lists:keysort(6,Done)}}.
	
%% @doc Return a list with all tasks in the project named ProjectName with status Status. The list is sorted by the ID:s of the tasks in increasing order. Return error if the project named ProjectName doesn't exist.
%% @spec taskStatusList(Projects, Status) -> list | error
%% where 
%%	Projects = string() + atom()
%%      ProjectName = string() + atom()
	
taskStatusList(ProjectID, Status) ->
	dets:open_file(integer_to_list(ProjectID), []),
	X = dets:match_object(integer_to_list(ProjectID),{'_','_','_','_',Status, '$1'}),
	dets:close(integer_to_list(ProjectID)),
	lists:keysort(6,X).


%% @doc If length of List = size of (DETS with name ProjectID) the task ID's in the DETS with the name "ProjectID" changes to the integer at the same position in the list List. If |List| is longer than DETS with name ProjectID, all objects in ProjectID with a corresponding id List gets the status Status and Changes the task ID's in the DETS with the name "ProjectID" to the integer at the same position in the list List. If lenght of List is shorter than DETS with name ProjectID nothing happens.
%% @spec sortTasks(ProjectID, Status, List, User) -> any()
%% where 
%%	ProjectID = integer()
%%     Status = atom()
%%     List = [integer()]
%%     User = string()

sortTasks(ProjectID, Status, List, User) ->	
	TaskList = taskStatusList(ProjectID, Status),
	IDList = lists:map(fun(X) -> element(6,X) end, TaskList),
	case length(IDList) > length(List) of
	false ->
		io:format("~ntest5!~n"),
		TaskList = taskStatusList(ProjectID, Status),
		IDList = lists:map(fun(X) -> element(6,X) end, TaskList),
		NameList = lists:map(fun(X) -> element(1,X) end, TaskList),		
		case length(List) == length(IDList) of
			true ->	
				dets:open_file(integer_to_list(ProjectID), []),
				sortTasksHelp(ProjectID, IDList, List),
				detsAbs(ProjectID, Status, NameList),
				dets:close(integer_to_list(ProjectID)),
				{ok, update};
			false ->
				io:format("~ntest7!~n"),
				dets:open_file(integer_to_list(ProjectID), []),
				[ID] = List -- IDList,
				changeTaskStatus(ProjectID, ID , Status, User),
				dets:sync(integer_to_list(ProjectID)),
				sortTasks(ProjectID,Status, List, User),
				dets:close(ProjectID)				
			end;		
	true ->
			{ok, origin}
	end.
	
%% @doc Changes the task ID's in the DETS with the name "ProjectID" to the integer at the same position in the list List   
%% @spec sortTasksHelp(ProjectID, IDList, List) -> atom()
%% where 
%%	ProjectID = integer()
%%     IDList = [integer()]
%%     List = [integer()]

sortTasksHelp(ProjectID, IDList, List) -> 
	case List == [] of
	false ->	
		case hd(IDList) == hd(List) of
		true -> 
			sortTasksHelp(ProjectID, tl(IDList), tl(List)),
			ok;
		false ->
			dets:open_file(integer_to_list(ProjectID), []),
			[Z] = dets:match_object(integer_to_list(ProjectID), {'_','_','_','_','_',hd(List)}),
			dets:insert(integer_to_list(ProjectID),{element(1,Z), element(2,Z), element(3,Z), element(4,Z), element(5,Z), (-hd(IDList))}),
			dets:close(integer_to_list(ProjectID)),
			sortTasksHelp(ProjectID, tl(IDList), tl(List))			
		end;

	_ ->
		ok
	end.

%% @doc All negative taskID's in the project with ID ProjectID are changed to the same positive value.   
%% @spec detsAbs(ProjectID, Status, NameList) -> atom() 
%% where 
%%      ProjectID = integer()
%%      Status = atom()
%%      NameList = [string()]

detsAbs(ProjectID, Status, NameList) ->
	
	case NameList == [] of
	
	false ->
		dets:open_file(integer_to_list(ProjectID), []),
		[X] = dets:match_object(integer_to_list(ProjectID), {hd(NameList),'_','_','_','_','_'}),

		case element(6,X) > 0 of
			false ->
				dets:insert(integer_to_list(ProjectID),{hd(NameList),element(2,X), element(3,X), element(4,X), element(5,X), -(element(6,X))}),
				dets:close(integer_to_list(ProjectID)),
				detsAbs(ProjectID, Status, tl(NameList));
			true ->
				detsAbs(ProjectID, Status, tl(NameList))
			end;
	true ->
		ok
	end.
	



	
	%%% EUnit test cases here %%%

%Kontrollerar att det gÃ¥r att lÃ¤gga till en task i ett projekt
add_task_test() -> 
	proman:newProject("project_test", "project1_test"),
	taskman:addTask(0, "task_test", "task_desc_test", "creator_test"),
  	?assertEqual(1, length(element(1,element(2,taskman:listTasks(0))))),
	taskman:deleteTask(0,1),
	proman:deleteProject("project_test", "project1_test"),	
	file:delete("project_test").

%Kontrollerar att man fÃ¥r ett error om man fÃ¶rsÃ¶ker lÃ¤gga till en task som redan finns
add_task_error_test() ->
	proman:newProject("project_test", "new_project_test"),
	taskman:addTask(0, "task_test", "task_desc_test", "creator_test"),
 	?assertMatch({error, _}, taskman:addTask(0, "task_test", "task_desc_test", "creator_test")),
	taskman:deleteTask(0,1),
	proman:deleteProject("project_test", "new_project_test"),	
	file:delete("project_test").

%Kontrollerar att det gÃ¥r att Ã¤ndra status pÃ¥ en task
change_task_status_test() ->
	proman:newProject("project_test", "project1_test"),
	taskman:addTask(0, "task_test", "task_desc_test", "creator_test"),
	taskman:changeTaskStatus(0, 1, inprogress, "user_test"),
	?assertMatch([{"task_test","task_desc_test","user_test","creator_test",inprogress,1}], element(2,element(2,taskman:listTasks(0)))),
	taskman:deleteTask(0,1),
	proman:deleteProject("project_test", "project1_test"),	
	file:delete("project_test").

%Kontrollerar att man fÃ¥r ett error om man fÃ¶rsÃ¶ker Ã¤ndra status pÃ¥ en task som inte finns
change_task_status_error_test() ->
	proman:newProject("project_test", "project1_test"),
	?assertMatch({error, _}, taskman:changeTaskStatus(0, 1, inprogress, "user_test")),
	proman:deleteProject("project_test", "project1_test"),
	file:delete("project_test").

%Kontrollerar att man fÃ¥r en tom lista om man Ã¤ndrar status pÃ¥ en task till nÃ¥got annat Ã¤n "working", "inprogress" eller "done"
change_task_status_bad_status_test() ->	
	proman:newProject("project_test", "project1_test"),
	taskman:addTask(0, "task_test", "task_desc_test", "creator_test"),
	taskman:changeTaskStatus(0, 1, inprogres, "user_test"),
	?assertMatch([], element(2,element(2,taskman:listTasks(0)))),
	taskman:deleteTask(0,1),
	proman:deleteProject("project_test", "project1_test"),	
	file:delete("project_test").

%Kontrollerar att det gÃ¥r att radera en task
delete_task_test() ->
	proman:newProject("project_test", "project1_test"),
	taskman:addTask(0, "task_test", "task_desc_test", "creator_test"),
	taskman:deleteTask(0, 1),
	?assertEqual(0, length(element(1,element(2,taskman:listTasks(0))))),
	taskman:deleteTask(0,1),
	proman:deleteProject("project_test", "project1_test"),	
	file:delete("project_test").

%Kontrollerar att man fÃ¥r ett error om man fÃ¶rsÃ¶ker radera en task som inte finns
delete_task_error_test() ->
	proman:newProject("project_test", "project1_test"),
	?assertMatch({error, _}, taskman:deleteTask(0,1)),
	proman:deleteProject("project_test", "project1_test"),
	file:delete("project_test").

%Kontrollerar att det gÃ¥r att lista alla task i ett projekt
list_tasks_test() ->
	proman:newProject("project_test", "project1_test"),
	taskman:addTask(0, "task_test", "task_desc_test", "creator_test"),		
	?assertEqual({ok,{[{"task_test","task_desc_test","creator_test","creator_test",waiting,1}],[],[]}}, taskman:listTasks(0)),
	taskman:deleteTask(0,1),
	proman:deleteProject("project_test", "project1_test"),
	file:delete("project_test").

%Kontrollerar att det returneras en tom lista nÃ¤r man listar alla tasks i ett projekt och det inte finns nÃ¥gra. 
list_tasks_empty_test() ->
	proman:newProject("project_test", "project1_test"),
	?assertEqual({ok,{[],[],[]}}, taskman:listTasks(0)),
	proman:deleteProject("project_test", "project1_test"),
	file:delete("project_test").


%Kontrollerar att det gÃ¥r att lista alla task med en specifik status
task_status_list_test() ->
	proman:newProject("project_test", "project1_test"),
	taskman:addTask(0, "task_test", "task_desc_test", "creator_test"),		
	?assertEqual([{"task_test","task_desc_test","creator_test","creator_test",waiting,1}], taskman:taskStatusList(0,waiting)),
	taskman:deleteTask(0,1),
	proman:deleteProject("project_test", "project1_test"),
	file:delete("project_test").

%Kontrollerar att man fÃ¥r en tom lista om man listar en status vars tasks har flyttats till en annan status
task_status_list_empty_test() ->
	proman:newProject("project_test", "project1_test"),
	taskman:addTask(0, "task_test", "task_desc_test", "creator_test"),
	taskman:changeTaskStatus(0,1, inprogress, "user_test"),	
	?assertEqual([], taskman:taskStatusList(0,waiting)),
	taskman:deleteTask(0,1),
	proman:deleteProject("project_test", "project1_test"),
	file:delete("project_test").
	
%Kontrollerar att sorteringsfunktionens hjÃ¤lpalgoritm byter ordning pÃ¥ ID pÃ¥ rÃ¤tt sÃ¤tt
sort_tasks_help_test() ->
	proman:newProject("project_test", "project1_test"),	
	taskman:addTask(0, "task1_test", "task1", "creator_test"),
	taskman:addTask(0, "task2_test", "task2", "creator_test"),
	taskman:addTask(0, "task3_test", "task3", "creator_test"),
	taskman:sortTasksHelp(0, [1,2,3], [2,1,3]),
	?assertMatch([{"task1_test","task1","creator_test","creator_test", waiting,-2},
   		{"task2_test","task2","creator_test","creator_test",waiting,-1},
    		{"task3_test","task3","creator_test","creator_test",waiting,3}], 
		element(1, element(2,taskman:listTasks(0)))),
	taskman:deleteTask(0,-1),
	taskman:deleteTask(0,-2),
	taskman:deleteTask(0,3),
	proman:deleteProject("project_test", "project1_test"),
	file:delete("project_test").

%Kontrollerar att sorteringsfunktionens hjÃ¤lpalgoritm ger en tom lista om ordningslistan fÃ¶r ID Ã¤r tom
sort_tasks_help_empty_test() ->
	proman:newProject("project_test", "project1_test"),	
	taskman:sortTasksHelp(0, [], []),
	?assertMatch([], element(1, element(2,taskman:listTasks(0)))),
	proman:deleteProject("project_test", "project1_test"),
	file:delete("project_test").

%Kontrollerar att alla ID fÃ¥r rÃ¤tt positiva vÃ¤rden
detsAbs_test() ->
	proman:newProject("project_test", "project1_test"),	
	taskman:addTask(0, "task1_test", "task1", "creator_test"),
	taskman:addTask(0, "task2_test", "task2", "creator_test"),
	taskman:addTask(0, "task3_test", "task3", "creator_test"),
	taskman:sortTasksHelp(0, [1,2,3], [2,1,3]),
	detsAbs(0, waiting, ["task1_test", "task2_test", "task3_test"]),
	?assertMatch([{"task2_test","task2","creator_test","creator_test", waiting,1},
		{"task1_test","task1","creator_test","creator_test",waiting,2},
		{"task3_test","task3","creator_test","creator_test",waiting,3}], 
		element(1, element(2,taskman:listTasks(0)))),
	taskman:deleteTask(0,1),
	taskman:deleteTask(0,2),
	taskman:deleteTask(0,3),
	proman:deleteProject("project_test", "project1_test"),
	file:delete("project_test").

%Kontrollerar att funktionen ger en tom lista om en tom lista matas in
detsAbs_empty_test() ->
	proman:newProject("project_test", "project1_test"),	
	detsAbs(0, waiting, []),
	?assertMatch([], element(1, element(2,taskman:listTasks(0)))),
	proman:deleteProject("project_test", "project1_test"),
	file:delete("project_test").

%Kontrollerar att ordningen blir rÃ¤tt dÃ¥ man flyttar en task inom en status
sort_tasks_test() ->
	proman:newProject("project_test", "project1_test"),	
	taskman:addTask(0, "task1_test", "task1", "creator_test"),
	taskman:addTask(0, "task2_test", "task2", "creator_test"),
	taskman:addTask(0, "task3_test", "task3", "creator_test"),
	taskman:sortTasks(0, waiting, [2,1,3], "user_test"),	
	?assertMatch([{"task2_test","task2","creator_test","creator_test",waiting,1},
      			{"task1_test","task1","creator_test","creator_test",waiting,2},
      		{"task3_test","task3","creator_test","creator_test",waiting,3}], 
		element(1, element(2,taskman:listTasks(0)))),
	taskman:deleteTask(0,1),
	taskman:deleteTask(0,2),
	taskman:deleteTask(0,3),
	proman:deleteProject("project_test", "project1_test"),
	file:delete("project_test").	


%Kontrollerar att ordningen i den nya statusen blir rÃ¤tt dÃ¥ en task flyttas frÃ¥n en status till en annan
sort_tasks_long_test() ->
	proman:newProject("project_test", "project1_test"),	
	taskman:addTask(0, "task1_test", "task1", "creator_test"),
	taskman:addTask(0, "task2_test", "task2", "creator_test"),
	taskman:addTask(0, "task3_test", "task3", "creator_test"),
	taskman:changeTaskStatus(0, 1, inprogress, "user_test"),
	taskman:changeTaskStatus(0, 2, inprogress, "user_test"),
	taskman:sortTasks(0, inprogress, [1,3,2], "user_test"),	
	?assertMatch([{"task1_test","task1","user_test","creator_test", inprogress,1},
      		{"task3_test","task3","user_test","creator_test",inprogress, 2},
      		{"task2_test","task2","user_test","creator_test",inprogress, 3}], 
		element(2, element(2,taskman:listTasks(0)))),
	taskman:deleteTask(0,1),
	taskman:deleteTask(0,2),
	taskman:deleteTask(0,3),
	proman:deleteProject("project_test", "project1_test"),
	file:delete("project_test").	

%Kontrollerar att ordningen i den gamla statusen blir rÃ¤tt dÃ¥ en task flyttas frÃ¥n en status till en annan
sort_tasks_short_test() ->
	proman:newProject("project_test", "project1_test"),	
	taskman:addTask(0, "task1_test", "task1", "creator_test"),
	taskman:addTask(0, "task2_test", "task2", "creator_test"),
	taskman:addTask(0, "task3_test", "task3", "creator_test"),
	taskman:sortTasks(0, inprogress, [1], "user_test"),	
	?assertMatch([{"task2_test","task2","creator_test","creator_test",waiting,2},
    		{"task3_test","task3","creator_test","creator_test",waiting,3}], 
		element(1, element(2,taskman:listTasks(0)))),
	taskman:deleteTask(0,1),
	taskman:deleteTask(0,2),
	taskman:deleteTask(0,3),
	proman:deleteProject("project_test", "project1_test"),
	file:delete("project_test").